#include "N1SHA.h"


N1SHA::N1SHA() 
{
    resetSHA();
}


N1SHA::~N1SHA() {}

N1NAME_DEF(N1SHA)

void N1SHA::resetSHA() 
{
    naState[0] = 0x67452301;
    naState[1] = 0xEFCDAB89;
    naState[2] = 0x98BADCFE;
    naState[3] = 0x10325476;
    naState[4] = 0xC3D2E1F0;
    naCount[0] = 0;
    naCount[1] = 0;
    memset(caBuff,0,64);
}


void N1SHA::updateSHA(const void* pData,unsigned int nLen) 
{
    unsigned int t = naCount[0];
    naCount[0] += (nLen<<3);
    if (naCount[0]<t)
    {
        naCount[1]++;
    }
    naCount[1] += (nLen>>29);
    t = (t>>3) & 0x3F;

    int Index = 0;
    if (t) 
    {
        Index = t;
        t = 64 - t;
        if (nLen<t) 
        {
            memcpy(caBuff+Index,pData,nLen);
            return;
        }
        memcpy(caBuff+Index,pData,t);
        transformSHA();
        nLen -= t;
    }

    while (nLen>=64) 
    {
        memcpy(caBuff,(char*)pData+Index,64);
        transformSHA();
        Index += 64;
        nLen -= 64;    
    }

    memcpy(caBuff,(char*)pData+Index,nLen);
}


void N1SHA::finalSHA() 
{
    unsigned int Cnt;
    unsigned char p;
  
    Cnt = (naCount[0]>>3) & 0x3F;
    p = Cnt;
    caBuff[p] = 0x80;
    p++;

    Cnt = 64 - 1 - Cnt;
    if (Cnt<8) 
    {
        memset(caBuff+p,0,Cnt);
        transformSHA();
        memset(caBuff,0,56);
    } 
    else 
    {
        memset(caBuff+p,0,Cnt-8);
    }
    naBuff[14] = naCount[1];
    naBuff[15] = naCount[0];
    reverseBytes(&naBuff[14],2);

    transformSHA();
    reverseBytes(&naState[0],5);
}


char* N1SHA::getPtrToHash() 
{
    return (char*)naState;
}


#define ROL(x,n) ((x<<n)|(x>>(32-n)))

void N1SHA::transformSHA() 
{
    unsigned int a, b, c, d, e;
    unsigned int Tmp;
    unsigned int W[16];
    int i;

    a = naState[0];
    b = naState[1];
    c = naState[2];
    d = naState[3];
    e = naState[4];

    memcpy(W,caBuff,64);
    reverseBytes((unsigned int*)&W,16);
  
    for(i=0;i<80;i++) 
    {
        if (i>15)
            W[i&15] = ROL( (W[i&15]^W[(i-14)&15]^W[(i-8)&15]^W[(i-3)&15]) ,1 );
        if (i<=19)
            Tmp = ROL(a,5) + e + W[i&15] + 0x5A827999 + ((b&c)|((~b)&d));
        if (i>=20 && i<=39)
            Tmp = ROL(a,5) + e + W[i&15] + 0x6ED9EBA1 + (b^c^d);
        if (i>=40 && i<=59)
            Tmp = ROL(a,5) + e + W[i&15] + 0x8F1BBCDC + ((b&c)|(b&d)|(c&d));
        if (i>=60 && i<=79)
            Tmp = ROL(a,5) + e + W[i&15] + 0xCA62C1D6 + (b^c^d);
        e = d;
        d = c;
        c = ROL(b,30);
        b = a;
        a = Tmp;
    }

    naState[0] += a;
    naState[1] += b;
    naState[2] += c;
    naState[3] += d;
    naState[4] += e;
} 

void N1SHA::reverseBytes(unsigned int *pBuff, int nLen) 
{
    unsigned int Tmp;
    int i;
    for (i=0;i<nLen;i++) 
    {
        Tmp = (pBuff[i]<<16) | (pBuff[i]>>16);
        pBuff[i] = ((Tmp & 0x00FF00FF)<<8) | ((Tmp & 0xFF00FF00)>>8);
    }
}

N1AnsiString* N1SHA::get()
{
    N1AnsiString* sOut = (new N1AnsiString())->autorelease<N1AnsiString>();
    sOut->setLength(41);
    char a[]="0123456789abcdef";
    char *pc;
    pc = getPtrToHash();
    unsigned char x,i;
    for (i=0;i<20;i++) 
    {
        x = pc[i];
        (*sOut)[i*2] = a[ x>>4 ];
        (*sOut)[i*2+1] = a[ x & 0x0F];
    }
    (*sOut)[40] = 0;

    return sOut;
}

N1AnsiString* N1SHA::getHash(pcstr str)
{
    resetSHA();
    updateSHA(str, strlen(str));
    finalSHA();

    return get();
}

#ifdef N1LIB_SELFTEST

N1TEST(N1SHATest)
{
    N1SHA sha;

    N1AutoreleasePool pool;

    N1AnsiString *s1;
    
    /* Samples from http://ru.wikipedia.org/wiki/SHA-1 */
    s1 = sha.getHash("The quick brown fox jumps over the lazy dog");

    N1CHECK(*s1 == "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", 1);
   
    s1 = sha.getHash("sha");
    N1CHECK(*s1 == "d8f4590320e1343a915b6394170650a8f35d6926", 2);

    s1 = sha.getHash("Sha");
    N1CHECK(*s1 == "ba79baeb9f10896a46ae74715271b7f586e74640", 3);

    s1 = sha.getHash("");

    N1CHECK(*s1 == "da39a3ee5e6b4b0d3255bfef95601890afd80709", 4);

    /* Samles from www.faqs.org/rfcs/rfc3174.html */

    #define TEST1   "abc"
    #define TEST2a  "abcdbcdecdefdefgefghfghighijhi"

    #define TEST2b  "jkijkljklmklmnlmnomnopnopq"
    #define TEST2   TEST2a TEST2b
    #define TEST3   "a"
    #define TEST4a  "01234567012345670123456701234567"
    #define TEST4b  "01234567012345670123456701234567"
    /* an exact multiple of 512 bits */
    #define TEST4   TEST4a TEST4b

    s1 = sha.getHash( TEST1 );
    N1CHECK(*s1 == "a9993e364706816aba3e25717850c26c9cd0d89d", 5);
    s1 = sha.getHash( TEST2 );
    N1CHECK(*s1 == "84983e441c3bd26ebaae4aa1f95129e5e54670f1", 6);

    uint32 i;
    sha.resetSHA();
    for(i=0;i<1000000;i++)
    { 
        sha.updateSHA(&TEST3, strlen(TEST3));
    }
    sha.finalSHA();
    s1 = sha.get();
    N1CHECK(*s1 == "34aa973cd4c4daa4f61eeb2bdbad27316534016f", 7);

    sha.resetSHA();
    for(i=0;i<10;i++)
    { 
        sha.updateSHA(&TEST4, strlen(TEST4));
    }
    sha.finalSHA();
    s1 = sha.get();
    N1CHECK(*s1 == "dea356a2cddd90c7a7ecedc5ebb563934f460452", 8);
}

#endif //N1LIB_SELFTEST
